﻿#ifndef __SMART_CAR_H__
#define __SMART_CAR_H__
#include <map>

#include "GoodsModel.h"
#include "JsColorModel.h"
#include "JsModelProxy.h"
#include "MapColorModel.h"
#include "RoadLineFarDirectionModel.h"
#include "RoadLineGrayModel.h"
#include "RoadLineModel.h"
#include "RoadLineNearDirectionModel.h"
#include "RoadLineUtils.h"
#include "RoadSignModel.h"
class SmartCar
{
public:
    SmartCar()
    {
        GetMapColorModel("map");
    }

    void SetRootDirectory(const std::string &root_directory)
    {
        GetRoadLineJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetRoadLineJsModelProxy().GetModelName()).string());
        // GetRoadLineGrayJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetRoadLineGrayJsModelProxy().GetModelName()).string());
        GetRoadLineNearDirectionJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetRoadLineNearDirectionJsModelProxy().GetModelName()).string());
        GetRoadLineFarDirectionJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetRoadLineFarDirectionJsModelProxy().GetModelName()).string());
        GetRoadSignJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetRoadSignJsModelProxy().GetModelName()).string());
        GetGoodsJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetGoodsJsModelProxy().GetModelName()).string());
        GetJsColorJsModelProxy().SetRootDirectory((std::filesystem::path(root_directory) / GetGoodsJsModelProxy().GetModelName()).string());

        for (auto it = m_map_color_models.begin(); it != m_map_color_models.end(); it++)
        {
            JsModelProxy proxy(it->second.get());
            proxy.SetRootDirectory((std::filesystem::path(root_directory) / "colors").string());
        }
    }

    JsModelProxy GetJsModelProxy(const std::string &model)
    {
        if (model == "roadlines")
        {
            return GetRoadLineJsModelProxy();
        }
        else if (model == "roadlines_near_direction")
        {
            return GetRoadLineNearDirectionJsModelProxy();
        }
        else if (model == "roadlines_far_direction")
        {
            return GetRoadLineFarDirectionJsModelProxy();
        }
        else if (model == "roadsigns")
        {
            return GetRoadSignJsModelProxy();
        }
        else if (model == "colors")
        {
            return GetJsColorJsModelProxy();
        }
        else
        {
            return GetGoodsJsModelProxy();
        }
    }

    const std::string GetBase64ImageData(const std::string &path)
    {
        if (std::filesystem::path(path).extension() == ".base64") return JsModelProxy::LoadFile(path);

        auto img = JsModelProxy::GetImageByImagePath(path);
        if (img == nullptr) return "";
        auto text        = img->GetImageData();
        auto base64_text = base64_encode(text);
        return "data:image/jpeg;base64," + base64_text;
    }

    std::string GetBase64ImageRGB565Data(const std::string &base64_image_data)
    {
        auto img = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        if (img == nullptr) return "";
        img         = img->Scale(0.1, 0.1);
        auto width  = img->GetWidth();
        auto height = img->GetHeight();
        std::ostringstream oss;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                unsigned char r = img->GetPixel(x, y, 0) * 0xff;
                unsigned char g = img->GetPixel(x, y, 1) * 0xff;
                unsigned char b = img->GetPixel(x, y, 2) * 0xff;
                int v           = (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3);
                oss << v << " ";
            }
            oss << std::endl;
        }
        return oss.str();
    }

    std::vector<unsigned char> GetBase64ImageRGBData(const std::string &base64_image_data)
    {
        auto img = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        return img == nullptr ? std::vector<unsigned char>() : *(img->GetImageRGBData());
    }

    std::string PredictRoadLineBase64ImageColor(const std::string &base64_image_data, const std::string &map_name)
    {
        auto start_time = get_timestamp();
        auto img        = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        if (img == nullptr) return "";
        img            = img->Scale(0.2, 0.2);
        auto step      = 2;
        auto threshold = step * step * 2 / 3;

        auto model             = GetMapColorModel(map_name);
        auto red_color_index   = model->GetRedClassifyIndex();
        auto white_color_index = model->GetWhiteClassifyIndex();
        auto black_color_index = model->GetBlackClassifyIndex();
        auto datas             = img->GetImageRGBData()->data();
        auto width             = img->GetWidth();
        auto height            = img->GetHeight();
        std::ostringstream oss;
        std::vector<int> colors;
        for (int y = 0; y < height; y += step)
        {
            for (int x = 0; x < width; x += step)
            {
                int red_count   = 0;
                int white_count = 0;
                int black_count = 0;
                for (int i = 0; i < step && white_count < threshold && black_count < threshold; i++)
                {
                    for (int j = 0; j < step && white_count < threshold && black_count < threshold; j++)
                    {
                        auto index       = ((y + i) * width + (x + j)) * 3;
                        auto color_index = model->Predict(datas[index], datas[index + 1], datas[index + 2]);
                        if (white_color_index == color_index) white_count++;
                        if (black_color_index == color_index) black_count++;
                        if (red_color_index == color_index) red_count++;
                    }
                }
                if (red_count >= threshold)
                {
                    oss << "2 ";
                    colors.push_back(2);
                }
                else if (white_count >= threshold)
                {
                    oss << "1 ";
                    colors.push_back(1);
                }
                else if (black_count >= threshold)
                {
                    oss << "0 ";
                    colors.push_back(0);
                }
                else
                {
                    oss << "-1 ";
                    colors.push_back(-1);
                }
            }
            oss << std::endl;
        }
        auto colors_width  = width / step;
        auto colors_height = height / step;
        // auto classify      = GetRoadLineGrayModel()->FixColorData(colors.data(), colors_width, colors_height);
        // oss << classify << std::endl;
        RoadLineUtils::FixedRoadLineColors(colors.data(), colors_width, colors_height);
        auto end_time = get_timestamp();
        // std::cout << "PredictRoadLineBase64ImageColor predict use time: " << (end_time - start_time) << "ms classify = " << Utf8ToString(classify) << std::endl;
        std::cout << "PredictRoadLineBase64ImageColor predict use time: " << (end_time - start_time) << "ms" << std::endl;

        return RoadLineUtils::GetColorsText(colors.data(), colors_width, colors_height);
        return oss.str();
    }

    std::string GetRoadLineClassifyBase64Image(const std::string &base64_image_data, const std::string &map_name) { return GetRoadLineClassify(JsModelProxy::GetImageByBase64ImageData(base64_image_data), map_name); }

    std::string GetRoadLineClassify(std::shared_ptr<DataImage> img, const std::string &map_name)
    {
        auto start_time = get_timestamp();
        if (img == nullptr) return "";
        img            = img->Scale(0.2, 0.2);
        auto step      = 2;
        auto threshold = step * step * 2 / 3;

        auto model             = GetMapColorModel(map_name);
        auto red_color_index   = model->GetRedClassifyIndex();
        auto white_color_index = model->GetWhiteClassifyIndex();
        auto black_color_index = model->GetBlackClassifyIndex();
        auto datas             = img->GetImageRGBData()->data();
        auto width             = img->GetWidth();
        auto height            = img->GetHeight();
        std::vector<int> colors;
        for (int y = 0; y < height; y += step)
        {
            for (int x = 0; x < width; x += step)
            {
                int red_count   = 0;
                int white_count = 0;
                int black_count = 0;
                for (int i = 0; i < step && white_count < threshold && black_count < threshold; i++)
                {
                    for (int j = 0; j < step && white_count < threshold && black_count < threshold; j++)
                    {
                        auto index       = ((y + i) * width + (x + j)) * 3;
                        auto color_index = model->Predict(datas[index], datas[index + 1], datas[index + 2]);
                        if (white_color_index == color_index) white_count++;
                        if (black_color_index == color_index) black_count++;
                        if (red_color_index == color_index) red_count++;
                    }
                }
                if (red_count >= threshold)
                {
                    colors.push_back(1);
                }
                else if (white_count >= threshold)
                {
                    colors.push_back(1);
                }
                else if (black_count >= threshold)
                {
                    colors.push_back(0);
                }
                else
                {
                    colors.push_back(-1);
                }
            }
        }
        auto colors_width  = width / step;
        auto colors_height = height / step;
        auto classify = RoadLineUtils::FixedRoadLineColors(colors.data(), colors_width, colors_height);
        auto end_time = get_timestamp();
        // auto result = RoadLineUtils::GetColorsText(colors.data(), colors_width, colors_height);
        std::cout << "GetRoadLineClassify predict use time: " << (end_time - start_time) << "ms" << " classify = " << Utf8ToString(classify) << std::endl;
        return classify;
    }

    void TrainBase64ImageColors(const std::string &base64_image_data, const std::string &color_indexs, const std::string &color_classify, const std::string &map_name)
    {
        auto img = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        if (img == nullptr) return;
        std::unordered_set<int> color_indexs_set;
        std::stringstream ss(color_indexs);
        std::string item;
        while (std::getline(ss, item, ' '))
        {
            if (item.empty()) continue;
            color_indexs_set.insert(std::stoi(item));
        }

        const int index_width  = 32;
        const int index_height = 24;
        const int index_step   = 10;
        const int img_width    = img->GetWidth();
        const int img_height   = img->GetHeight();
        auto model             = GetMapColorModel(map_name);
        auto classify_index    = model->CreateGetClassifyIndex(color_classify);
        auto dataset           = model->GetDataSet();
        for (int y = 0; y < index_height; y++)
        {
            for (int x = 0; x < index_width; x++)
            {
                auto index = y * index_width + x;
                if (color_indexs_set.find(index) != color_indexs_set.end())
                {
                    std::cout << "x = " << x << " y = " << y << " " << color_classify << std::endl;
                    for (int i = 0; i < index_step; i++)
                    {
                        for (int j = 0; j < index_step; j++)
                        {
                            int img_x        = x * index_step + j;
                            int img_y        = y * index_step + i;
                            unsigned char r  = img->GetPixel(img_x, img_y, 0) * 0xff;
                            unsigned char g  = img->GetPixel(img_x, img_y, 1) * 0xff;
                            unsigned char b  = img->GetPixel(img_x, img_y, 2) * 0xff;
                            auto color_index = model->GetColorIndex(r, g, b);
                            dataset->insert_or_assign(color_index, classify_index);
                        }
                    }
                }
            }
        }
        model->SaveDataSet();
        JsModelProxy::FlushFileSystem();
    }

    std::string DownLoadColorDataSet(const std::string &map_name)
    {
        auto model   = GetMapColorModel(map_name);
        auto dataset = model->GetDataSet();
        std::ostringstream oss;
        for (auto it = dataset->begin(); it != dataset->end(); it++)
        {
            oss << it->first << " " << model->GetIndexClassify(it->second) << std::endl;
        }
        return oss.str();
    }

    std::string PredictBase64ImageColor(const std::string &base64_image_data, const std::string color, const std::string &map_name)
    {
        auto start_time = get_timestamp();
        auto img        = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        if (img == nullptr) return "";
        img = img->Scale(0.2, 0.2);

        auto model       = GetMapColorModel(map_name);
        auto color_index = model->CreateGetClassifyIndex(color);
        auto datas       = img->GetImageRGBData()->data();
        auto width       = img->GetWidth();
        auto height      = img->GetHeight();
        auto step        = 2;
        auto threshold   = step * step * 2 / 3;
        std::ostringstream oss;
        for (int y = 0; y < height; y += step)
        {
            for (int x = 0; x < width; x += step)
            {
                int count = 0;
                for (int i = 0; i < step && count < threshold; i++)
                {
                    for (int j = 0; j < step && count < threshold; j++)
                    {
                        auto index = ((y + i) * width + (x + j)) * 3;
                        if (model->Predict(datas[index], datas[index + 1], datas[index + 2]) == color_index) count++;
                    }
                }
                if (count >= threshold)
                {
                    oss << "1 ";
                }
                else
                {
                    oss << "0 ";
                }
            }
            oss << std::endl;
        }
        auto end_time = get_timestamp();
        std::cout << "PredictBase64ImageColor predict use time: " << (end_time - start_time) << "ms" << std::endl;
        return oss.str();
    }

    std::string PredictBase64ImageRoadLine(const std::string &base64_image_data)
    {
        return GetRoadLineJsModelProxy().Predict(base64_image_data);
    }

    std::string PredictBase64ImageRoadLineGray(const std::string &base64_image_data)
    {
        return GetRoadLineGrayJsModelProxy().Predict(base64_image_data);
    }

    std::string PredictBase64ImageRoadLineNearDirection(const std::string &base64_image_data)
    {
        return GetRoadLineNearDirectionJsModelProxy().Predict(base64_image_data);
    }

    std::string PredictBase64ImageRoadLineFarDirection(const std::string &base64_image_data)
    {
        return GetRoadLineFarDirectionJsModelProxy().Predict(base64_image_data);
    }

    std::string PredictBase64ImageRoadSign(const std::string &base64_image_data)
    {
        return GetRoadSignJsModelProxy().Predict(base64_image_data);
    }

    std::string PredictBase64ImageRoadSignRect(const std::string &base64_image_data)
    {
        auto start_time = get_timestamp();
        // auto img        = JsModelProxy::GetImageByBase64ImageData(base64_image_data);
        // if (img == nullptr) return "";
        // auto model = JsColorModel::GetInstance();
        // auto rects = model->GetColorRect(img, model->GetRedClassifyIndex(), 0.2);
        // auto rects = model->GetRedRect(img, 0.2);
        auto width = 0;
        auto height = 0;
        auto pixels = JsModelProxy::LoadImageRGBByBase64ImageData(base64_image_data, &width, &height);
        if (pixels == nullptr) return "";
        auto rects = RoadLineUtils::GetRedRect(pixels, width, height, 0.2);
        if (rects == nullptr) return "";
        std::ostringstream oss;
        oss << rects[0] << " " << rects[1] << " " << rects[2] << " " << rects[3];
        auto end_time = get_timestamp();
        std::cout << "PredictBase64ImageRoadSignRect use time: " << (end_time - start_time) << "ms" << std::endl;
        return oss.str();
    }

    std::string GetRoadSignImageRGBDatas(const std::string &base64_image_data)
    {
        auto width = 0;
        auto height = 0;
        auto pixels = JsModelProxy::LoadImageRGBByBase64ImageData(base64_image_data, &width, &height);
        if (pixels == nullptr) return "";
        auto datas = RoadLineUtils::GetRedRectDatas(pixels, width, height, 0.2, 32, 24);
        return datas;
    }

    JsColorModel *GetMapColorModel(const std::string &map_name)
    {
        if (map_name == "none") return GetJsColorModel();
        auto it = m_map_color_models.find(map_name);
        if (it != m_map_color_models.end()) return it->second.get();
        auto model = std::make_shared<MapColorModel>(map_name);
        m_map_color_models.insert_or_assign(map_name, model);
        return model.get();
    }

public:
    inline RoadSignModel *GetRoadSignModel() { return RoadSignModel::GetInstance(); }
    inline RoadLineModel *GetRoadLineModel() { return RoadLineModel::GetInstance(); }
    inline RoadLineNearDirectionModel *GetRoadLineNearDirectionModel() { return RoadLineNearDirectionModel::GetInstance(); }
    inline RoadLineFarDirectionModel *GetRoadLineFarDirectionModel() { return RoadLineFarDirectionModel::GetInstance(); }
    inline RoadLineGrayModel *GetRoadLineGrayModel() { return RoadLineGrayModel::GetInstance(); }
    inline GoodsModel *GetGoodsModel() { return GoodsModel::GetInstance(); }
    inline JsColorModel *GetJsColorModel() { return JsColorModel::GetInstance(); }
    inline JsModelProxy GetRoadSignJsModelProxy() { return JsModelProxy(GetRoadSignModel()); }
    inline JsModelProxy GetRoadLineJsModelProxy() { return JsModelProxy(GetRoadLineModel()); }
    inline JsModelProxy GetRoadLineNearDirectionJsModelProxy() { return JsModelProxy(GetRoadLineNearDirectionModel()); }
    inline JsModelProxy GetRoadLineFarDirectionJsModelProxy() { return JsModelProxy(GetRoadLineFarDirectionModel()); }
    inline JsModelProxy GetRoadLineGrayJsModelProxy() { return JsModelProxy(GetRoadLineGrayModel()); }
    inline JsModelProxy GetGoodsJsModelProxy() { return JsModelProxy(GetGoodsModel()); }
    inline JsModelProxy GetJsColorJsModelProxy() { return JsModelProxy(GetJsColorModel()); }
    inline JsModelProxy GetMapColorJsModelProxy(const std::string &map_name) { return JsModelProxy(GetMapColorModel(map_name)); }

protected:
    std::string m_root_directory;
    std::unordered_map<std::string, std::shared_ptr<MapColorModel>> m_map_color_models;
};
#endif